/* 
 *  boot/setup.S 
 *
 *  last modified 2007.07.14 21:31 by ChenJie
 *
 *  主要功能：
 * 	1.遵守 Linux 引导协议中的 old linux 版本, 可以被 grub 引导
 * 	2.由 grub 引导后, CS:IP = 0x9020:0x0000, SS:SP = 0x9000:0xFF00
 * 	3.setup 先在 real-mode 使用 BIOS 获取一些基本参数, 
 *	  保存在 0x90000 ~ 0x90200
 * 	4.setup 设置 8259A 中断控制器
 * 	5.setup 关闭中断, 进入 protected-mode
 * 	6.setup 将 system 移动到合适的位置
 * 	7.setup 跳转到 0x0000:0x00000000 处开始执行
 */

#include <asm/boot.h>
#include <asm/setup.h>
.global _start
.text
.code16
_start:
	jmp	startup_16

# This is the setup header, and it must start at %cs:2 (old 0x9020:2)
magic_number:	.ascii	"OLD."		# "HdrS" for Linux Boot Protocol
boot_version:	.word	0x0011		# for linux0.11
realmode_swtch:	.word	0, 0
start_sys_seg:	.word	0x1000
kernel_version:	.word	0
type_of_loader:	.byte	0
loadflags:	.byte	0
setup_move_size:.word	0
code32_start:	.long	0
ramdisk_image:	.long	0
ramdisk_size:	.long	0
bootsect_kludge:.long	0
heap_end_ptr:	.word	0
pad1:		.word	0
cmd_line_ptr:	.long	0
ramdisk_max:	.long	0

startup_16:	call start_of_setup
		.align	16		# 16个字节对齐
# The offset at this point is 0x240
#		.space	(0xeff-0x240 + 1)

# End of setup header ####################################################

start_of_setup:
	movw	%cs, %ax		# cs = 0x9020
	movw	%ax, %ds		# ds = 0x9020

	movw	$INITSEG, %ax
	movw	%ax, %es		# es = 0x9000
	
	# 获取当前光标位置
	movb	$0x03, %ah		# ah = 0x03, 读光标功能号
	xorb	%bh, %bh		# bh = 0x00, 页号
	int	$0x10			# int 0x10, 
	movw	%dx, %es:0		#BYTE[0x9000:0x00]--->列号
					#BYTE[0x9000:0x01]--->行号
	
	# 获取内存大小(从1MB处开始的内存大小, 以KB为单位)
	movb	$0x88, %ah		# ah = 0x88, 功能号
	int	$0x15			# int 0x15
	movw	%ax, %es:2		# WORD[0x9000:0x02]--->扩展内存大小

	# 获取video-card 的当前显示模式
	movb	$0x0f, %ah		# ah = 0x0f, 功能号
	int	$0x10			# int 0x10
	movw	%bx, %es:4	 	# BYTE[0x9000:0x04]--->当前显示页
	movw	%ax, %es:6		# BYTE[0x9000:0x06]--->当前显示模式
					# BYTE[0x9000:0x07]--->字符列数

	# 检查 EGA/VGA 和一些配置参数
	movb	$0x12, %ah		# ah = 0x12, 功能号
	movb	$0x10, %bl		# bl = 0x10, 功能号
	int	$0x10			# int 0x10
	movw	%ax, %es:8		# WORD[0x9000:0x08]--->?
	movw	%bx, %es:10		# BYTE[0x9000:0x0A]--->安装的显卡内存
					# BYTE[0x9000:0x0B]--->显示状态
	movw	%cx, %es:12		# WORD[0x9000:0x0C]--->显卡特性参数

	# 获取 hd0 数据
	xorw	%ax, %ax
	movw	%ax, %ds		# ds = 0x0000
	lds	%ds:4*0x41, %si		# [0x0000:0x41*4]--->ds:si
	movw	$0x80, %di		# es:di = 0x9000:0x0080
	movw	$0x10, %cx		# cx = 0x10
	rep movsb			# copy(ds:si, es:di, cx);

	# 获取 hd1 数据
	xorw	%ax, %ax
	movw	%ax, %ds		# ds = 0x0000
	lds	%ds:4*0x46, %si		# [0x0000:0x46*4]-->ds:si
	movw	$0x90, %di		# es:di = 0x9000:0x0090
	movw	$0x10, %cx		# cx = 0x10
	rep movsb			# copy(ds:si, es:di, cx)

	# 检查 hd1 是否存在
	movb	$0x15, %al		# ah = 0x15, 功能号
	movb	$0x81, %dl		# dl = 0x81, 0x8X是硬盘, 0x80是第一个
					#    硬盘, 0x81是第二个硬盘
	int	$0x13			# int 0x13
					# 输出: ah 类型码
	jc	no_disk1		# ah = 0x00, 则 cf=1, 没有这个盘
					# ah = 0x01, 是软驱, 无change line支持
					# ah = 0x02, 是软驱(或其它移动设备)
	cmp	$3, %ah			# ah = 0x03, 是硬盘
	je	is_disk1
no_disk1:
	movw	$INITSEG, %ax		
	movw	%ax, %es		# [es:di]--->指向hd1数据贮存区
	movw	$0x90, %di		
	movw	$0x10, %cx		# cx = 0x10
	xorw	%ax, %ax		# ax = 0
	rep stosb			# clear(es:di, cx);
is_disk1:

	jmp 	move_to_protected_mode

init_8259A:
	movb	$0x11, %al	# ICW1, 00010001B:有 ICW4 , 级联, 边沿触发
	outb	%al, $0x20	# 发送到 8259 主片偶地址
	nop
	outb	%al, $0xA0	# 发送到 8259 从片偶地址
	nop

	movb	$0x20, %al	# ICW2, 主片, 设置中断从 0x20 开始
	outb	%al, $0x21	# 发送到 8259 主片奇地址
	nop
	movb	$0x28, %al	# 从片, 设置中断从 0x28 开始
	outb	%al, $0xA1	# 发送到 8259 从片奇地址
	nop

	movb	$0x04, %al	# ICW3, 00000100B: 主片的IR2脚接从片
	outb	%al, $0x21	# 发送到 8259 主片奇地址
	nop
	movb	$0x02, %al	# 00000010B: 从片接在主片的IR2引脚
	outb	%al, $0xA1	# 发送到 8259 从片的奇地址
	nop

	movb	$0x01, %al	# ICW4, 00000001B: 8086 模式, 正常 EOI, 非缓冲
	outb	%al, $0x21	# 发送到 8259 主片的奇地址
	nop
	outb	%al, $0xA1	# 发送到 8259 从片的奇地址
	nop

	movb	$0xff, %al	# OCW1, 11111111B: 屏蔽所有中断 MASK WORD
	outb	%al, $0x21	# 发送到主片的奇地址
	nop
	outb	%al, $0xA1	# 发送到从片的奇地址

	
	# 准备进入保护模式......
move_to_protected_mode:
	cli			# no interrupts allowed!
	movw	%cs, %ax
	movw	%ax, %ds	# 由于前面改变了 ds, 当要使用内存操作数时
				# 就要注意其值是否正确了....
				# 开始的时候我没有及时把 ds 恢复, 所以加载
				# gdt_48 就不正确，因而只要一进入保护模式
				# 就会引起 protection exception.

	lgdt	gdt_48		# 加载gdt

	inb	$0x92, %al	# 开启A20地址线
	orb	$0x02, %al
	outb	%al, $0x92

	movl	%cr0, %eax	# 置保护模式位 PE = 1
	orl	$0x00000001, %eax
	movl	%eax, %cr0
	
jmpi:					#jmpi	0x90200 + move_system, $8
	.byte	0x66, 0xea		# jmp move_system
	.long   move_system + 0x00090200
	.word	0x0008
	
	
test:
	movw	%cs, %ax
	movw	%ax, %ds
	lea	message, %si
	call	prtstr
loop:
	jmp	loop
	ret

# Routine to print asciiz string at ds:si
prtstr:
	lodsb
	andb	%al, %al
	jz	fin
	call	prtchr
	jmp	prtstr
fin:	ret

# Space printing
prtsp2: call	prtspc		# Print double space
prtspc:	movb	$0x20, %al	# Print single space

# part of above routine, this one just prints ascii al
prtchr:	pushw	%ax
	pushw	%cx
	movw	$7, %bx
	movw	$0x01, %cx
	movb	$0x0e, %ah
	int	$0x10
	popw	%cx
	popw	%ax
	ret

beep:	movb	$0x07, %al
	jmp	prtchr

message:	.string "boot loader is OK!"

.align 4	# 4个字节对齐
.code32
move_system:
	movw	$0x10, %ax		# cs = 0x08
	movw	%ax, %gs		# gs = 0x10
	movw	$0x10, %ax		
	movw	%ax, %ds		# ds = 0x10
	movw	%ax, %es		# es = 0x10
	movw	%ax, %ss		# ss = 0x10
	movl	$0x9ff00, %esp		# esp = 0x0009ff00

	movl	$0x20000, %ecx		# ecx = 512KB/4
	movl	$0x10000, %esi		# esi = 0x00010000 ---> kernel
	movl	$0x0, %edi		# edi = 0x00000000 ---> kernel
	cld				# move(esi, edi, ecx)
	rep movsl

jmp_to_system:
	ljmp	$0x0008, $0x0000	# jmp offset 0 of segment 8 (cs)
					# 看上去ljmp word, word, 那其就只能
					# 转移64K范围内的地址, 和实模式下一样
					# 不知道为什么？
	

.align 16			# Intel 推荐采用16个字节对齐方式
gdt:
gdt0:	.word	0x0000		#  0 ~ 15 : limit  0 ~ 15

	.word	0x0000		#  0 ~ 15 : base address  0 ~ 15

	.word	0x0000		#  0 ~  7 : base address 16 ~ 23
				#  	8 : Accesses bit
				#		A=0 处理器未访问该段
				#		A=1 处理器已访问该段
				#  9 ~ 11 : TYPE
				#	当 S=1 时对TYPE(9~11位)
				#		0 只读
				#		1 读/写
				#		2 只读, 向下扩展
				#		3 读/写, 向下扩展
				#		4 只执行
				#		5 执行/读
				#		6 只执行, 一致码段
				#		7 执行/读, 一致码段
				#	当 S=0 时对TYPE+A(8~11位)
				#		0 <未定义>
				#		1 可用286TSS
				#		2 LDT
				#		3 忙的286TSS
				#		4 286调用门
				#		5 任务门
				#		6 286中断门
				#		7 286陷阱门
				#		8 <未定义>
				#		9 可用的386TSS
				#		A <未定义>
				#		B 忙的386TSS
				#		C 386调用门
				#		D <未定义>
				#		E 386中断门
				#		F 386陷阱门
				#      12 : S
				#		S=1 时为存储段描述符
				#		S=0 时为系统段描述符或门描述符
				# 13 ~ 14 : Descriptor Privilege Level
				#		DPL=0
				#		DPL=1
				#		DPL=2
				#		DPL=3
				#      15 : Present
				#		P=0 时该描述符加载时发出异常
				#		P=1 时可正常加载该描述符

	.word	0x0000		#  0 ~  3 : limit 16 ~ 19
				#  	4 : AVL 软件可利用位
				#  	5 : 0
				#	6 : D 一般取D=1
				#		D=0 时系统使用的是16位寄存器
				#		D=1 时系统使用的是32位寄存器
				#	7 : Granularity 
				#		G=0 时limit单元值是1字节
				#		G=1 时limit单元值是4KB字节
				#  8 ~ 15 : base address 24 ~ 31

gdt1:	.word	0xFFFF		# limit  = 0xfffff, 4GB
	.word	0x0000		# base address = 0
	.word	0x9A00		# code read/exec
	.word	0x00CF		# granularity = 4096, 386

gdt2:	.word	0xFFFF		# limit = 0xfffff, 4GB
	.word	0x0000		# base address = 0
	.word	0x9200		# data read/write
	.word	0x00CF		# granularity = 4096, 386

.align 4			# 4个字节对齐
	.word	0
idt_48:
	.word	0
	.long	0

.align 4			# 4个字节对齐
	.word	0
gdt_48:
	.word	3*8-1		# gdt limit = 24-1Byte, 3 GDT entries
	.long	0x90200+gdt	# gdt base = 0x90200 + gdt


